/**************************************************************/
/* CS/COE 1541				 			
   just compile with gcc -o pipeline pipeline.c			
   and execute using							
   ./pipeline  /afs/cs.pitt.edu/courses/1541/short_traces/sample.tr	0 0
***************************************************************/

/*
Modified by:

Joe Yusko

Drew McDermott

*/

#include <stdio.h>
#include <string.h>
#include "trace_item.h"


#define TRACE_BUFSIZE 1024*1024

#define BRANCH_HASHSIZE 256

static FILE *trace_fd;
static int trace_buf_ptr;
static int trace_buf_end;

static struct trace_item *trace_buf;
static struct trace_item *stage_buf[5];
static unsigned int branch_hash[BRANCH_HASHSIZE];

static unsigned int hash_mask = 0x7FE00000;

static struct trace_item noop = {0, 0, 0, 0, 0, 0};
static struct trace_item squash = {0, 0, 0, 0, 0, 0};

enum{ ti_SQUASH = 9 };

static int hazard = 0;
static int branch_sq = 0;
static struct trace_item hazard_buf;

void trace_init()
{
	
  trace_buf = malloc(sizeof(struct trace_item) * TRACE_BUFSIZE);

  if (!trace_buf) {
    fprintf(stdout, "** trace_buf not allocated\n");
    exit(-1);
  }

  trace_buf_ptr = 0;
  trace_buf_end = 0;
  
	
	noop.type = ti_NOP;
	squash.type = ti_SQUASH;
	
	
  int i = 0;
  for(i = 0 ; i < 5; i++)
  {
	stage_buf[i] = &noop;
  }
  for(i=0; i < BRANCH_HASHSIZE; i++)
  {
	branch_hash[i] = 0; //Assume NOT TAKEN
  }
  
}

void trace_uninit()
{
  free(trace_buf);
  fclose(trace_fd);
}

int trace_get_item(struct trace_item **item)
{
  int n_items;

  if (trace_buf_ptr == trace_buf_end) {	/* if no more unprocessed items in the trace buffer, get new data  */
    n_items = fread(trace_buf, sizeof(struct trace_item), TRACE_BUFSIZE, trace_fd);
    if (!n_items) return 0;				/* if no more items in the file, we are done */

    trace_buf_ptr = 0;
    trace_buf_end = n_items;			/* n_items were read and placed in trace buffer */
  }

  *item = &trace_buf[trace_buf_ptr];	/* read a new trace item for processing */
  trace_buf_ptr++;

  return 1;
}

int store_hash(int PC, char val)
{
	int masked_PC = PC & hash_mask; //0x7FE00000
	masked_PC = masked_PC >> 21;    //Logical Right Shift 21
	
	int index = masked_PC % BRANCH_HASHSIZE;
	/*
	printf("%x\n",index);
	getchar();
	*/
	
	branch_hash[index] = val;	

	return 0;
}

int load_hash(int PC)
{
	int masked_PC = PC & hash_mask; //0x7FE00000
	masked_PC = masked_PC >> 21;    //Logical Right Shift 21
	
	int index = masked_PC % BRANCH_HASHSIZE;

	return branch_hash[index];
}

int hazard_detec()
{
	if(stage_buf[2]->type == ti_LOAD) //If EX is a Load
	{
		if(stage_buf[2]->dReg == stage_buf[1]->sReg_a)
		{

			return 1;
		}
		else if(stage_buf[2]->dReg == stage_buf[1]->sReg_b)
		{

			return 1;
		}
	
	}
	return 0;
	
}

int branch_detec(int bp_meth)
{


	if(stage_buf[2]->type == ti_BRANCH)
	{
	
		if(bp_meth == 0) // NOT TAKEN
		{
			if(stage_buf[2]->Addr == stage_buf[1]->PC) //Branch taken
			{
				return 2;
			}
		
		}
		else if(bp_meth == 1) // TAKEN
		{
			if(stage_buf[2]->Addr == stage_buf[1]->PC) //Branch taken
			{
				return 2;
			}
			else  //Not taken
			{
				return 1;
			}
		
		}
		else // PREDICTOR
		{
			int predict = load_hash(stage_buf[2]->PC);
			

			if(predict == 0)
			{
				if(stage_buf[2]->Addr == stage_buf[1]->PC) //Branch taken
				{
					store_hash(stage_buf[2]->PC, 1);
					return 2;
				}
			}
			else
			{
				if(stage_buf[2]->Addr != stage_buf[1]->PC) //Branch taken
				{
					store_hash(stage_buf[2]->PC, 0);
					return 2;
				}
			}
			
		}
	}
	else
	{
		return 0;
	}
	return 0;
}


int print_stage(struct trace_item *tr_entry)
{

	switch(tr_entry->type) {
        case ti_NOP:
          printf("    NOP:") ;
          break;
        case ti_RTYPE:
          printf("  RTYPE:") ;
          break;
        case ti_ITYPE:
          printf("  ITYPE:") ;
          break;
        case ti_LOAD:
          printf("   LOAD:") ;
          break;
        case ti_STORE:
          printf("  STORE:") ;
          break;
        case ti_BRANCH:
          printf(" BRANCH:") ;
          break;
        case ti_JTYPE:
          printf("  JTYPE:") ;
          break;
        case ti_SPECIAL:
          printf("SPECIAL:") ;
          break;
        case ti_JRTYPE:
          printf(" JRTYPE:") ;
          break;
		case ti_SQUASH:
			printf(" SQUASH:");
			break;
      }
      printf(" (PC: %x) (sReg_a: %d) (sReg_b: %d) (dReg: %d) (addr: %x)", tr_entry->PC, tr_entry->sReg_a, tr_entry->sReg_b, tr_entry->dReg, tr_entry->Addr);
	
}
int adv_stage(struct trace_item *tr_entry, int trace_view, int cycle_num, int bp_meth)
{
	int debug = 1;
	
	int i,j = 0;
	for(i = 4; i > 0; i--)
	{
	
		if(i == 1)
		{	
			//Branch Mode 1 - Branch Not Taken
			if(branch_sq == 1 && bp_meth == 1)
			{
				break;
			}
		}
		else if(i == 2)
		{
			//Hazard
			if(hazard == 1)
			{
				
				break;
			}
			//Branch Mode Not 1
			if(branch_sq > 0 && bp_meth != 1)
			{
				break;
			}
			//Branch Mode 1 - Branch Taken
			if(branch_sq == 2 && bp_meth == 1)
			{
				break;
			}
		}
		
		
		stage_buf[i] = stage_buf[i-1];
	}
	if(hazard == 1)
	{
		//printf("\n HAZARD \n");
		stage_buf[2] = &noop;
		
	}
	else if(branch_sq > 0)
	{
		if(bp_meth != 1)
		{
			branch_sq--;
			stage_buf[2] = &squash;
		}
		else
		{
			stage_buf[branch_sq] = &squash;
			branch_sq = 0;
		}
	}
	else
	{
		stage_buf[0] =  tr_entry;
		debug = 0;
	}
	
	
	if(trace_view)
	{
		printf("\n[Cycle %d]",cycle_num);
		
		for(j = 0; j < 5; j++)
		{
			if(j == 0)
			{
				printf("\nIF  Stage: ");
			}
			else if(j == 1)
			{
				printf("\nID  Stage: ");
			}
			else if(j ==2)
			{
				printf("\nEX  Stage: ");
			}
			else if(j ==3)
			{
				printf("\nMEM Stage: ");
			}
			else
			{
				printf("\nWB  Stage: ");
			}
		
			print_stage(stage_buf[j]);
		}
		printf("\n");
		//getchar();
		
	}
	if(debug == 1)
	{
		//getchar();
		
	}
	hazard = hazard_detec();
	if(branch_sq == 0)
	{
		branch_sq = branch_detec(bp_meth);
	}

}



int main(int argc, char **argv)
{
  struct trace_item *tr_entry;
  size_t size;
  char *trace_file_name;
  int trace_view_on = 0;
  int prediction_method = 0;
  int unsigned stage_pc;
  
  unsigned char t_type = 0;
  unsigned char t_sReg_a= 0;
  unsigned char t_sReg_b= 0;
  unsigned char t_dReg= 0;
  unsigned int t_PC = 0;
  unsigned int t_Addr = 0;
  int i = 0;
  int j = 0;
  char str[10] = "";


  

  unsigned int cycle_number = 0;
  
	
  
  
  if (argc <= 3) {
    fprintf(stdout, "\nUSAGE: tv <trace_file> <switch - 0 or 1><prediction - 0, 1 or 2\n");
    fprintf(stdout, "\n(switch) to turn on or off individual item view.");
    fprintf(stdout, "\n(prediction) 0 branch not taken.\n");
    fprintf(stdout, "             1 branch always taken\n");
    fprintf(stdout, "             2 branch uses a one-bit branch predictor\n\n");
    exit(0);
  }
    
  trace_file_name = argv[1];
  if (argc == 4)
  { 
    trace_view_on = atoi(argv[2]);
    if(trace_view_on == 1)
      trace_view_on = 1;
    else
      trace_view_on = 0;
	  
	prediction_method = atoi(argv[3]);
  }
  fprintf(stdout, "\n ** opening file %s\n", trace_file_name);

  trace_fd = fopen(trace_file_name, "rb");

  if (!trace_fd) {
    fprintf(stdout, "\ntrace file %s not opened.\n\n", trace_file_name);
    exit(0);
  }

  trace_init();

  
	if(trace_view_on)
	{
		printf("\n\n[Cycle %d]",cycle_number);
		
		for(j = 0; j < 5; j++)
		{
			if(j == 0)
			{
				printf("\nIF  Stage: ");
			}
			else if(j == 1)
			{
				printf("\nID  Stage: ");
			}
			else if(j ==2)
			{
				printf("\nEX  Stage: ");
			}
			else if(j ==3)
			{
				printf("\nMEM Stage: ");
			}
			else
			{
				printf("\nWB  Stage: ");
			}
		
			print_stage(stage_buf[j]);
		}

	}
	
  while(1) {
	cycle_number++;
	if(hazard == 1 || branch_sq > 0)
	{
		adv_stage(&noop, trace_view_on, cycle_number, prediction_method);
	}
	else
	{
		size = trace_get_item(&tr_entry);
		
	   
		if (!size) {       /* no more instructions (trace_items) to simulate */

			int noops = 0;
			for(j = 0; j < 5; j++)
			{
				if(stage_buf[j] == &noop)
				{
					noops++;
				}
			}
			if(noops >= 5)
			{
				printf("\n\n+ Simulation terminates at cycle : %u\n", cycle_number-1);
				break;
			}
			else
			{
				adv_stage(&noop, trace_view_on, cycle_number, prediction_method);
			}
		}
		else{    
		/* parse the next instruction to simulate */
		
		
		  
		  adv_stage(tr_entry, trace_view_on, cycle_number, prediction_method);
		  

	   }  
   }

// SIMULATION OF A SINGLE CYCLE cpu IS TRIVIAL - EACH INSTRUCTION IS EXECUTED
// IN ONE CYCLE
/*
    if (trace_view_on) { //print the executed instruction if trace_view_on=1 
	
      switch(tr_entry->type) {
        case ti_NOP:
          printf("[cycle %d] entry NOP:",cycle_number) ;
          strcpy(str, "NOP");
          break;
        case ti_RTYPE:
          printf("[cycle %d] entry RTYPE:",cycle_number) ;
          strcpy(str, "RTYPE");
          break;
        case ti_ITYPE:
          printf("[cycle %d] entry ITYPE:",cycle_number) ;
          strcpy(str, "ITYPE");
          break;
        case ti_LOAD:
          printf("[cycle %d] entry LOAD:",cycle_number) ;
          strcpy(str, "LOAD");
          break;
        case ti_STORE:
          printf("[cycle %d] entry STORE:",cycle_number) ;
          strcpy(str, "STORE");
          break;
        case ti_BRANCH:
          printf("[cycle %d] entry BRANCH:",cycle_number) ;
          strcpy(str, "BRANCH");
          break;
        case ti_JTYPE:
          printf("[cycle %d] entry JTYPE:",cycle_number) ;
          strcpy(str, "JTYPE");
          break;
        case ti_SPECIAL:
          printf("[cycle %d] entry SPECIAL:",cycle_number) ;
          strcpy(str, "SPECIAL");
          break;
        case ti_JRTYPE:
          printf("[cycle %d] entry JRTYPE:",cycle_number) ;
          strcpy(str, "JRTYPE");
          break;
      }
	  */
	  /*
      printf(" (PC: %x) (sReg_a: %d) (sReg_b: %d) (dReg: %d) (addr: %x)\n", tr_entry->PC, tr_entry->sReg_a, tr_entry->sReg_b, tr_entry->dReg, tr_entry->Addr);
      cycle_number--;
      cycle_number = stage_fetch(pipe, t_sReg_a, t_sReg_b, t_dReg, t_PC, cycle_number);      
      cycle_number = stage_decode(pipe, str, t_sReg_a, t_sReg_b, t_dReg, t_Addr, cycle_number);
      cycle_number = stage_execute(pipe, t_sReg_a, t_sReg_b, t_dReg, cycle_number);
      cycle_number = stage_memory(pipe, cycle_number);
      cycle_number = stage_writeback(pipe, cycle_number);
      print_pipeline(pipe);
	  

    }*/
  }

  trace_uninit();

  exit(0);
}




